package cn.edu.cuit.elena.db;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;

import cn.edu.cuit.elena.common.Configurable;
import cn.edu.cuit.elena.common.Identifiable;
import cn.edu.cuit.elena.common.SackConstants;

public class DataSource
    implements ConnectionPool, Identifiable
{
    public static final String READY_STATE = "ready";
    public static final String NOT_CONNECTION = "notconnection";

    private static int MAX_CONNECTIONS = SackConstants.MAX_CONNECTION_NUM;
    private static int MIN_CONNECTIONS = SackConstants.MIN_CONNECTION_NUM;

    private LinkedList<Connection> connections;
    private Configurable dataSourceConfiguration;
    private String url;
    private String userName;
    private String passWord;
    private String driver;
    private Schema schema;
    private String name;
    private String state;
    private String vender;
    private String datasourceId;

    public static DataSource create( Configurable configurable )
    {
        try
        {
            Class.forName( configurable.getProperty( SackConstants.DRIVER ) );
            return new DataSource( configurable );
        }
        catch( ClassNotFoundException e )
        {
            throw new DataSourceException( e.getMessage() );
        }
        catch( SQLException e )
        {
            if( e.getMessage().startsWith( "No suitable driver" ) )
            {
                return new DataSource( configurable, true );
            }
            else
            {
                throw new DataSourceException( e.getMessage() );
            }
        }
    }

    public static DataSource create( Configurable configurable, boolean flag )
    {
        if( flag )
        {
            return new DataSource( configurable, flag );
        }
        return null;
    }

    private DataSource(Configurable configurable, boolean flag)
    {
        if( flag )
        {
            driver = configurable.getProperty( SackConstants.DRIVER ) == null ? "" : configurable
                .getProperty( SackConstants.DRIVER );
            url = configurable.getProperty( SackConstants.URL ) == null ? "" : configurable
                .getProperty( SackConstants.URL );
            userName = configurable.getProperty( SackConstants.USER_NAME ) == null ? "" : configurable
                .getProperty( SackConstants.USER_NAME );
            passWord = configurable.getProperty( SackConstants.PASS_WORD ) == null ? "" : configurable
                .getProperty( SackConstants.PASS_WORD );
            name = configurable.getProperty( SackConstants.SCHEMA_NAME ) == null ? "" : configurable
                .getProperty( SackConstants.SCHEMA_NAME );
            vender = configurable.getProperty( SackConstants.VENDER ) == null ? "" : configurable
                .getProperty( SackConstants.VENDER );
            datasourceId = configurable.getProperty( SackConstants.DATASOURCE_ID ) == null ? "" : configurable
                .getProperty( SackConstants.DATASOURCE_ID );

            state = NOT_CONNECTION;
        }
    }

    private DataSource(Configurable configurable) throws SQLException
    {
        dataSourceConfiguration = configurable;
        driver = configurable.getProperty( SackConstants.DRIVER );
        url = configurable.getProperty( SackConstants.URL );
        userName = configurable.getProperty( SackConstants.USER_NAME );
        passWord = configurable.getProperty( SackConstants.PASS_WORD );
        name = configurable.getProperty( SackConstants.SCHEMA_NAME ) == null ? "" : configurable
            .getProperty( SackConstants.SCHEMA_NAME );
        vender = configurable.getProperty( SackConstants.VENDER ) == null ? "" : configurable
            .getProperty( SackConstants.VENDER );
        datasourceId = configurable.getProperty( SackConstants.DATASOURCE_ID ) == null ? "" : configurable
            .getProperty( SackConstants.DATASOURCE_ID );
        createConnectionPool();
        state = READY_STATE;
    }

    private void createConnectionPool() throws SQLException
    {
        connections = new LinkedList<Connection>();
        for( int i = 0; i < MAX_CONNECTIONS; i++ )
        {
            connections.add( DriverManager.getConnection( url, userName, passWord ) );
        }
    }

    /**
     * Get connection from connection pool, use <code>synchronized</code> block to guarantee working
     * correctly.
     * */
    public Connection getConnection()
    {
        synchronized( connections )
        {
            //            if( READY_STATE.equals( state ) )
            //            {
            if( connections.size() == 0 )
            {
                try
                {
                    for( int i = 0; i < MIN_CONNECTIONS; i++ )
                    {
                        connections.add( DriverManager.getConnection( url, userName, passWord ) );
                    }
                }
                catch( SQLException e )
                {
                    throw new DataSourceException( e.getMessage() );
                }
                //                }
            }
            else if( NOT_CONNECTION.equals( state ) )
            {
                throw new NotForConnectionException( "Invalidate data source." );
            }

            return connections.pop();
        }
    }

    /**
     * Get connection from connection pool, use <code>synchronized</code> block to guarantee working
     * correctly.
     * */
    public void returnConnection( Connection connection )
    {
        synchronized( connections )
        {
            //            if( READY_STATE.equals( state ) )
            //            {
            if( connections.size() == MAX_CONNECTIONS )
            {
                try
                {
                    connection.close();
                }
                catch( SQLException e )
                {
                    e.printStackTrace();
                    throw new DataSourceException( e.getMessage() );
                }
                return;
                //                }
            }
            else if( NOT_CONNECTION.equals( state ) )
            {
                throw new NotForConnectionException( "Invalidate data source." );
            }
        }

        connections.add( connection );
    }

    public int getDBCode()
    {
        // TODO Auto-generated method stub
        return 0;

    }

    public boolean equals( Object o )
    {
        if( !( o instanceof DataSource ) )
        {
            return false;
        }
        return ( (DataSource) o ).driver == this.driver && ( (DataSource) o ).userName == this.userName
            && ( (DataSource) o ).passWord == this.passWord && ( (DataSource) o ).url == this.url;
    }

    public String getUrl()
    {
        return url;
    }

    public String getUserName()
    {
        return userName;
    }

    public String getPassWord()
    {
        return passWord;
    }

    public String getDriver()
    {
        return driver;
    }

    /**
     * Override.
     * 
     * @return url + userName + passWord + driver;
     * */
    public String toString()
    {
        return url + userName + passWord + driver;
    }

    public Schema loadSchema() throws SQLException
    {

        if( schema == null )
        {
            schema = new Schema( this );
        }

        return schema;

    }

    public Schema loadSchema( String userContextName ) throws SQLException
    {

        if( schema == null || schema.getUserContextName() == null )
        {
            schema = new Schema( this, userContextName );
        }

        return schema;

    }

    /**
     * @return return schema Name
     * */
    public String getName()
    {

        return name;
    }

    /**
     * @return Return the "userName+schemaName" in configuration.
     * */
    @Override
    public String getIdentifier()
    {
        return userName + name;
    }

    public String getState()
    {
        return state;
    }

    public Configurable getDataSourceConfiguration()
    {
        return dataSourceConfiguration;
    }

    public String getVender()
    {
        return vender;
    }

    public void setVender( String vender )
    {
        this.vender = vender;
    }

    public String getDatasourceId()
    {
        return datasourceId;
    }

    public void setDatasourceId( String datasourceId )
    {
        this.datasourceId = datasourceId;
    }
}
